#include "CanDriver.h"

#include <algorithm>

#include "private/CanReader.h"
#include "private/CanReaper.h"
#include "private/CanSender.h"
#include "private/LoggerConfigs.h"

namespace canpp {
    using std::move;
    using std::vformat;
    using std::format_args;
    using std::make_format_args;

    template<CanFrameConvertible MsgT>
    CanDriver<MsgT>::~CanDriver() {
    }

    void setLoggerConfig(string &config, const bool console, const bool textfile) {
        const string console_config = console
                                          ? "appenders_config.appender_0.enable=true"
                                          : "appenders_config.appender_0.enable=false";
        const string textfile_config = textfile
                                           ? "appenders_config.appender_1.enable=true"
                                           : "appenders_config.appender_1.enable=false";
        config = vformat(R"(
            #type
            appenders_config.appender_0.type=console
            {}
            appenders_config.appender_1.type=text_file
            {}
            #time_zone
            appenders_config.appender_0.time_zone=default local time
            appenders_config.appender_1.time_zone=default local time
            #log_level
            appenders_config.appender_0.levels=[debug,info,warning,error,fatal]
            appenders_config.appender_1.levels=[debug,info,warning,error,fatal]
            appenders_config.appender_1.file_name={}
            appenders_config.appender_1.max_file_size=10000000)",
                         format_args(make_format_args(
                             console_config, textfile_config,
                             "{}")));
    }

    void loggingConfigure(LoggingConfig &&config) {
        if (!loggingConfigured) {
            setLoggerConfig(reaperLoggerConfig, config.reaperLoggingToConsole, config.reaperLoggingToTextfile);
            setLoggerConfig(readerLoggerConfig, config.readerLoggingToConsole, config.readerLoggingToTextfile);
            setLoggerConfig(senderLoggerConfig, config.senderLoggingToConsole, config.senderLoggingToTextfile);
            loggingConfigured = true;
        }
    }

    template<CanFrameConvertible MsgT>
    CanDriver<MsgT>::CanDriver(CanDriverConfig &&config) {
        this->interface = std::move(config.canInterface);
        this->id = std::move(config.id.value_or(std::to_string(random_number(0, 2000))));
        loggingConfigure();

        if (config.canMask.has_value()) {
            this->reader = std::make_unique<CanReader>(this->interface, this->id,
                                                       config.canMask);
        } else {
            this->reader = std::make_unique<CanReader>(this->interface, this->id);
        }
        this->sender = std::make_unique<CanSender>(this->interface, this->id);
    }

    template<CanFrameConvertible MsgT>
    void CanDriver<MsgT>::registerReadingCallback(function<void(MsgT &&)> &func) {
        reader->registerCallback(std::move([func](const can_frame &&frame) {
            func(static_cast<MsgT>(frame));
        }));
    }

    template<CanFrameConvertible MsgT>
    void CanDriver<MsgT>::registerReadingCallback(function<void(MsgT &&)> &&func) {
        reader->registerCallback(std::move([func = std::move(func)](const can_frame &&frame) {
                func(static_cast<MsgT>(frame));
            }
        ));
    }

    template<CanFrameConvertible MsgT>
    MsgT CanDriver<MsgT>::canPeekMessage() {
        return move(static_cast<MsgT>(reader->peekMessage()));
    }

    template<CanFrameConvertible MsgT>
    void CanDriver<MsgT>::canSendMessage(const MsgT &&msg, optional<uint16_t> &&canId) {
        auto frame = static_cast<can_frame>(move(msg));
        frame.can_id = canId.value_or(frame.can_id);

        sender->canSend(can_frame(frame));
        reader->pushMessage(move(frame));
    }


    template class CanDriver<can_frame>;
}
